﻿using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.IO;
using System.Json;
using System.Linq;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using CrmAttachmentImage.CrmRestSdk;
using ImageTools;
using ImageTools.IO;
using ImageTools.IO.Gif;
using ImageTools.IO.Jpeg;
using ImageTools.IO.Png;

namespace CrmAttachmentImage
{
    public partial class MainPage
    {
        private static Dictionary<string, string> _extensions;
        private bool _allowUpdate;
        private bool _hideButtons;
        
        public MainPage(Guid id, string otn, string data)
        {
            var par = Parameters.Parameterize(data);

            InitializeComponent();
            InitializeElements(id, otn, par);
            //HtmlPage.RegisterScriptableObject("Image", this);
        }

        private void ShowMessage(string message, bool useMessageBox = false, string title = "Image", MessageBoxButton buttons = MessageBoxButton.OK)
        {
            if (useMessageBox)
            {
                MessageBox.Show(message, title, buttons);
                return;
            }
            HideButtons = true;
            ImageSelector.Visibility = Visibility.Collapsed;
            Image.Visibility = Visibility.Collapsed;
            Message.Text = message;
            Message.Visibility = Visibility.Visible;
        }

        [ScriptableMember]
        public bool AllowUpdate
        {
            get { return _allowUpdate; }
            set
            {
                _allowUpdate = value;
                ImageSelector.Visibility = _allowUpdate ? Visibility.Visible : Visibility.Collapsed;
                if (!HideButtons)
                {
                    DeleteButton.Visibility = ImageSelector.Visibility;
                    UploadButton.Visibility = ImageSelector.Visibility;
                }
                Image.AllowDrop = _allowUpdate;
            }
        }

        public Guid? AttachmentId { get; set; }
        public string AttachmentIdFieldName { get; set; }
        public EntityReference CallingObject { get; private set; }
        public string FilePrefix { get; set; }
        public string FileSubject { get; set; }
        public int MaxImageWidth { get; set; }
        public int MaxImageHeight { get; set; }
        public int MaxWebCamImageWidth { get; set; }
        public int MaxWebCamImageHeight { get; set; }

        [ScriptableMember]
        public bool ShowWebCamButton
        {
            get
            {
                return (WebCamButton.Visibility == Visibility.Visible);
            }
            set { WebCamButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed; }
        }

        [ScriptableMember]
        public bool HideButtons
        {
            get { return _hideButtons; }
            set
            {
                _hideButtons = value;
                UploadButton.Visibility = !_hideButtons ? Visibility.Visible : Visibility.Collapsed;
                DeleteButton.Visibility = UploadButton.Visibility;
            }
        }

        private static Dictionary<string, string> Extensions
        {
            get
            {
                return _extensions ?? (_extensions = new Dictionary<string, string>
                                                     {
                                                         {".png", "image/png"},
                                                         {".jpg", "image/jpeg"},
                                                         {".jpeg", "image/jpeg"}
                                                     });
            }
        }

        private crmContext Context { get; set; }


        public Byte[] OpenImage(FileInfo file, out string filename, out string mimetype)
        {
            filename = file.Name;
            mimetype = string.Empty;

            // check extension validity
            var extension = file.Extension.ToLower();
            if (!Extensions.ContainsKey(extension))
            {
                return null;
            }
            mimetype = Extensions[extension];

            // Read the file, and return the converted imageData as a byte array
            var stream = file.OpenRead();
            var imageData = new Byte[stream.Length + 2];
            // The extra length allows the file to have a closing mark, which is required by SetSource.
            stream.Read(imageData, 0, Convert.ToInt32(stream.Length - 1));
            stream.Close();
            return imageData;
        }

        public void RetrieveImageAttachments()
        {
            // Don't do this if the drop down is not visible
            if (ImageSelector.Visibility == Visibility.Collapsed) return;

            var query =
                (DataServiceQuery<Annotation>)Context.AnnotationSet.Where(
                        a => a.FileName.StartsWith(FilePrefix) &&
                        (a.FileName.EndsWith(".jpg") ||
                        a.FileName.EndsWith(".jpeg") ||
                        a.FileName.EndsWith(".png")) &&
                            // Thanks WalterN - Can't believe I fort to match the Id!
                        a.ObjectId.Id == CallingObject.Id
                 );

            
            query.BeginExecute(OnRetrieveImageAttachmentsComplete, new object[] { "RetrieveImageAttachments", query });
        }

        private void CreateNote(string filename, string mimetype, byte[] imageData)
        {
            var base64 = Convert.ToBase64String(imageData);
            var storedFileName = FilePrefix + filename;

            var note = new Annotation();
            note.ObjectId = CallingObject;
            note.Subject = FileSubject.Replace("%s", filename);
            note.NoteText = "Image: " + filename;
            note.MimeType = mimetype;
            note.FileName = storedFileName;
            note.DocumentBody = base64;
            Context.AddToAnnotationSet(note);
            Context.BeginSaveChanges(OnCreateNoteComplete, note);
        }

        [ScriptableMember(ScriptAlias = "Remove")]
        public void Delete()
        {
            SetAttachmentId(String.Empty);
            SetSource();
            ImageSelector.SelectedValue = null;
        }

        private void DeleteButtonPressed(object sender, MouseButtonEventArgs e)
        {
            Delete();
        }

        private void ImageDrop(object sender, DragEventArgs e)
        {
            if (!AllowUpdate)
            {
                return;
            }

            var dataFiles = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];

            if (dataFiles == null)
            {
                return;
            }

            var imageFiles = (from file in dataFiles
                              where Extensions.ContainsKey(file.Extension.ToLower())
                              select file).ToList();

            // Check for images
            if (imageFiles.Count < 1)
            {
                return;
            }

            var imageFile = imageFiles[0];
            string filename, mimetype;
            var imageData = OpenImage(imageFile, out filename, out mimetype);
            SetSource(imageData);
            CreateNote(filename, mimetype, imageData);
        }

        private void InitializeElements(Guid id, string otn, Dictionary<string, string> par)
        {
            MaxImageWidth = Parameters.GetIntegerParameter(par, "max-width");
            MaxImageHeight = Parameters.GetIntegerParameter(par, "max-height");
            MaxWebCamImageWidth = Parameters.GetIntegerParameter(par, "max-webcam-width", MaxImageWidth);
            MaxWebCamImageHeight = Parameters.GetIntegerParameter(par, "max-webcam-height", MaxImageHeight);

            AttachmentIdFieldName = par.ContainsKey("field") ? par["field"] : string.Empty;
            FileSubject = par.ContainsKey("subject") ? par["subject"] : "Image Attachment";
            FilePrefix = par.ContainsKey("prefix") ? par["prefix"] : "form-img-";
            var saveFirst = par.ContainsKey("savefirst")
                                ? par["savefirst"]
                                : "Please save the record to attach an image.";

            ShowWebCamButton = (par.ContainsKey("webcam") && par["webcam"].ToLower() == "true");

            if ((id == Guid.Empty ||
                 (Utility.FormType != Utility.FormTypes.Update &&
                  Utility.FormType != Utility.FormTypes.ReadOnly &&
                  Utility.FormType != Utility.FormTypes.NotSet &&
                  Utility.FormType != Utility.FormTypes.Undefined)) ||
                String.IsNullOrEmpty(AttachmentIdFieldName))
            {
                string message;
                if (id == Guid.Empty)
                {
                    message = saveFirst;
                }
                else if (String.IsNullOrEmpty(AttachmentIdFieldName))
                {
                    message = "There was no attachment field specified in the parameters of the Silverlight control.";
                }
                else
                {
                    message = "The image control is not available when viewing a form in this mode.";
                }

                // Show the message
                ShowMessage(message);
                return;
            }

            // Set the calling object
            CallingObject = new EntityReference {Id = id, LogicalName = otn.ToLower()};

            // Setup Context
            Context = new crmContext(Utility.CombineUrl(Utility.ServerUrl, "/XRMServices/2011/OrganizationData.svc"));

            // Load the currently selected image
            var noteIdString = Utility.GetEntityValue(AttachmentIdFieldName, String.Empty);

            if (!String.IsNullOrEmpty(noteIdString))
            {
                var noteIdGuid = new Guid(noteIdString);
                AttachmentId = noteIdGuid;
                SetSource(AttachmentId.Value);
            }


            // Set HideButtons
            HideButtons = ((par.ContainsKey("hidebuttons") &&
                            String.Equals(par["hidebuttons"], "false", StringComparison.OrdinalIgnoreCase))
                           || Utility.FormType == Utility.FormTypes.ReadOnly);

            // Set Allow Update
            // This ignores the privelege
            AllowUpdate = //Utility.AttributePriveledge(AttachmentIdFieldName, Utility.AttributePrivilege.Update) &&
                Utility.FormType != Utility.FormTypes.ReadOnly;

            // Load the drop down
            if (AllowUpdate)
            {
                RetrieveImageAttachments();
            }
            else
            {
                ShowWebCamButton = false;
            }
        }



        private void OnCreateNoteComplete(IAsyncResult result)
        {
            try
            {
                Context.EndSaveChanges(result);
                var createdNote = result.AsyncState as Annotation;
                if (createdNote != null)
                {
                    var id = createdNote.AnnotationId;
                    AttachmentId = id;
                    SetAttachmentId(AttachmentId.ToString());
                }
                RetrieveImageAttachments();
            }
            catch (SystemException)
            {
            }
            //throw new NotImplementedException();
        }

        private void OnRetrieveImageAttachmentsComplete(IAsyncResult ar)
        {
            // get results
            var oArray = ar.AsyncState as object[];

            if (oArray == null || oArray.Length < 2 || oArray[0] as string != "RetrieveImageAttachments") return;

            var query = oArray[1] as DataServiceQuery<Annotation>;

            if (query == null)
            {
                return;
            }

            // get attachments
            var attachments = (from a in query.EndExecute(ar)
                               let filename = a.FileName
                               let noteid = a.AnnotationId
                               select new KeyValuePair<string, object>(filename, noteid))
                .ToList();

            // Sort by file name
            attachments.Sort((a, b) => a.Key.CompareTo(b.Key));

            // Bind to drop down
            Dispatcher.BeginInvoke(() =>
            {
                ImageSelector.ItemsSource = attachments;
                if (AttachmentId.HasValue)
                {
                    ImageSelector.SelectedValue = AttachmentId.Value;
                }
            });
        }

        private void SetAttachmentId(string attachmentId)
        {
            // Update AttachmentIdFieldName's value
            try
            {
                Dispatcher.BeginInvoke(() => Utility.SetEntityValue(AttachmentIdFieldName, attachmentId));
                return;
            }
            catch { }

            // If it failed, try setting it via JSON instead of setting the entity value

            try
            {
                var idfield = CallingObject.LogicalName + "id";
                var set = CallingObject.LogicalName[0].ToString().ToUpper() + CallingObject.LogicalName.Substring(1) +
                          "Set";
                //

                if (CallingObject.Id != null)
                {
                    var entity = new JsonObject();
                    entity[idfield] = CallingObject.Id.Value.ToString();
                    entity[AttachmentIdFieldName.ToLower()] = attachmentId;
                    //
                    Context.AttachTo(set, entity);
                    Context.UpdateObject(entity);
                }
            }
            catch
            {
                MessageBox.Show("There was an error saving the image.");
            }
        }

        private void SetSource(ImageSource source)
        {
            Image.Source = source;
            Image.Stretch = Stretch.Uniform;
        }

        private void SetSource()
        {
            var sr =
                Application.GetResourceStream(new Uri("/CrmAttachmentImage;component/Images/noimage.png",
                                                      UriKind.Relative));
            var bmp = new BitmapImage();
            bmp.SetSource(sr.Stream);
            Image.Source = bmp;
            Image.Stretch = Stretch.None;
        }

        [ScriptableMember(ScriptAlias = "LoadImage")]
        public void SetSource(Guid noteid)
        {
            if (Utility.GetEntityValue(AttachmentIdFieldName, string.Empty) != noteid.ToString())
            {
                SetAttachmentId(noteid.ToString());
            }
            var query = (DataServiceQuery<Annotation>)Context.AnnotationSet.Where(a => a.AnnotationId == noteid);
            query.BeginExecute(OnAnnotationRetrieved, new object[] { "SetSource", query });
        }

        private void OnAnnotationRetrieved(IAsyncResult ar)
        {
            // get results
            var oArray = ar.AsyncState as object[];

            if (oArray == null || oArray.Length < 2 || oArray[0] as string != "SetSource") return;

            var query = oArray[1] as DataServiceQuery<Annotation>;

            if (query == null) return;
            
            var note = (from a in query.EndExecute(ar) select a).ToArray();

            if (note.Count() == 0) return;

            var imageData = Convert.FromBase64String(note[0].DocumentBody);

            SetSource(imageData);
        }

        private void SetSource(Byte[] imageData)
        {
            // WalterN's code correction
            var bitmapimage = new BitmapImage();
            var stream = new MemoryStream(imageData);
            // // The only reason these two lines were here is because it was failing on some PNGs - it may not be needed.
            // stream.Position = 0;
            // stream.SetLength(imageData.Length - 1);
            bitmapimage.SetSource(stream);
            stream.Close();
            SetSource(bitmapimage);
        }

        [ScriptableMember]
        public void Upload()
        {
            const string imageFilter =
                "All Image Files|*.png;*.jpg;*.jpeg;*.gif | PNG Files (*.png)|*.png; | JPG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg | GIF Files (*.gif)|*.gif";

            if (!AllowUpdate || Image == null)
            {
                return;
            }


            var ofd = new OpenFileDialog {Filter = imageFilter, Multiselect = false};

            if (false == ofd.ShowDialog())
            {
                return;
            }

            var file = ofd.File;
            string filename = file.Name;

            var img = new ExtendedImage();
            string mimetype = null;
            IImageEncoder encoder = null;

            switch (file.Extension.ToUpper())
            {
                case ".JPG":
                case ".JPEG":
                    var jpegDecoder = new JpegDecoder();
                    jpegDecoder.Decode(img, file.OpenRead());
                    mimetype = "image/jpeg";
                    encoder = new JpegEncoder();
                    break;
                case ".PNG":
                    var pngDecoder = new PngDecoder();
                    pngDecoder.Decode(img, file.OpenRead());
                    mimetype = "image/png";
                    encoder = new PngEncoder();
                    break;
                case ".GIF":
                    var gifDecoder = new GifDecoder();
                    gifDecoder.Decode(img, file.OpenRead());
                    mimetype = "image/png";
                    filename.Remove(filename.Length - 5, 4);
                    filename = String.Format("{0}.png", filename);
                    encoder = new PngEncoder();
                    break;
            }

            ExtractImageAndSaveNote(ResizeImage(img), encoder, filename, mimetype, true);

        }

        private ExtendedImage ResizeImage(ExtendedImage img, double maxheight = -1, double maxwidth = -1)
        {
            if (maxheight == -1) maxheight = MaxImageHeight;
            if (maxwidth == -1) maxwidth = MaxImageWidth;

            double scaleX = 0, scaleY = 0;
            if (maxheight > 0 && img.PixelHeight > maxheight)

                scaleY = maxheight / img.PixelHeight;

            if (maxwidth > 0 && img.PixelWidth > maxwidth)

                scaleX = maxwidth / img.PixelWidth;

            // maintain aspect ratio by picking the most severe scale

            double scale = Math.Min(scaleY, scaleX);

            if (scale == 0) return img;

            int w = Convert.ToInt32(img.PixelWidth * scale);
            int h = Convert.ToInt32(img.PixelHeight * scale);

            return ExtendedImage.Resize(img, w, h, new ImageTools.Filtering.BilinearResizer());
        }

        private void UploadButtonPressed(object sender, MouseButtonEventArgs e)
        {
            Upload();
        }

        private void ImageSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (ImageSelector.SelectedValue == null) return;
            var noteIdGuid = (Guid)ImageSelector.SelectedValue;
            SetSource(noteIdGuid);
        }

        #region Marco Amoedo extension - webcam capture

        private CaptureSource _captureSource;
        private VideoBrush _webcamBrush;

        private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
        {
            WebCam_Initialize();
        }


        private void WebCam_Initialize()
        {
            _captureSource = new CaptureSource();

            // async capture failed event handler
            _captureSource.CaptureFailed += CaptureSource_CaptureFailed;

            // async capture completed event handler
            _captureSource.CaptureImageCompleted += CaptureSource_CaptureImageCompleted;
        }

        /// <summary>
        /// Event handler for the Web Cam button
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WebCam_ButtonPressed(object sender, MouseButtonEventArgs e)
        {
            // If started revert visibility
            if (_captureSource.State == CaptureState.Started)
            {
                _captureSource.CaptureImageAsync();

                // Revert visibility
                Image.Visibility = Visibility.Visible;
                Video.Visibility = Visibility.Collapsed;
                return;
            }

            // Set the video capture source the WebCam.
            _captureSource.VideoCaptureDevice =
                CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();

            // set the source on the VideoBrush used to display the video
            _webcamBrush = new VideoBrush();
            _webcamBrush.SetSource(_captureSource);
            _webcamBrush.Stretch = Stretch.Uniform;


            // Set the Fill property of the Rectangle to the VideoBrush.
            Video.Fill = _webcamBrush;

            //Hide the image to show the video
            Image.Visibility = Visibility.Collapsed;
            Video.Visibility = Visibility.Visible;

            // Request access to device and verify the VideoCaptureDevice is not null.
            if (!CaptureDeviceConfiguration.RequestDeviceAccess() || _captureSource.VideoCaptureDevice == null)
            {
                ShowMessage("Could not start Webcam. Verify device is connected " +
                                "and privacy permission allow access to device.", true, "WebCam Capture");
                return;
            }

            try
            {
                _captureSource.Start();
            }
            catch (InvalidOperationException ex)
            {
                // Notify user that the webcam could not be started.
                ShowMessage("There was a problem starting the webcam " +
                                "If using a Mac, verify default device settings." +
                                "Right click app to access the Configuration settings." + "\n " + ex.Message, true, "WebCam Capture");
                return;
            }
        }

        private void CaptureSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
        {
            // Set the ImageBrush to the WriteableBitmap
            var imageData = e.Result.ToImage();

            var filename = String.Format("webcam_{0}.png", DateTime.Now.ToFileTimeUtc());

            ExtractImageAndSaveNote(ResizeImage(imageData, MaxWebCamImageWidth, MaxWebCamImageHeight), new PngEncoder(),
                                    filename, "image/png");
            SetSource(e.Result);
            _captureSource.Stop();

        }

        private void ExtractImageAndSaveNote(ExtendedImage imageData, IImageEncoder encoder, string filename, string mimetype, bool setsource = false)
        {
            var stream = new MemoryStream();
            encoder.Encode(imageData, stream);
            byte[] ba = stream.ToArray();

            if (setsource)
            {
                SetSource(ba);
            }

            CreateNote(filename, mimetype, ba);

            stream.Close();
        }

        private void CaptureSource_CaptureFailed(object sender, ExceptionRoutedEventArgs e)
        {
            ShowMessage("Could not capture Image. Verify device is connected " +
                                   "and privacy permission allow access to device.", true, "WebCam Capture");
        }

        #endregion
    }
}